// 作用，创造一个类似于v-loading一样的指令
// 在base/loading 和no-result组件中使用
// 注意使用的时候需要在main.js中进行注册(import使用的js文件和使用directive函数注册 参见main.js)

// 自定义v-组件指令
import { createApp } from 'vue'
// import Loading from './loading' 废弃，使用参数传入
import { addClass, removeClass } from '@/assets/js/dom'

// 该样式添加在了assets/scss/base.scss中，.g-relative{position: relative}
const relativeCls = 'g-relative'

/*
* 参数如果传一个loading组件那么就创建v-loading指令，如传no-result那么就创建v-no-result指令
* Comp 就是import进入的组件
* */
export default function createLoadingLikeDirective(Comp) {
  const directive = {
    // 主要写一些钩子函数 在钩子中去实现逻辑
    // 指令主要是将loading组件生成的DOM动态插入到指令作用的DOM对象上（v-loading=true），如果v-loading=false那么就删除动态插入的
    // 指令挂载时的钩子函数
    mounted(el, binding) {
      // el指向指令所在的dom 如 <div v-loading="true" id="box"> 那么el就是#box binding.value就是代表的true
      // 判断v-loading值为true动态插入到指令作用的节点下
      // 如果创建组件对应的dom？先用这个loading组件新建一个vue实例（app对象），然后再动态取挂载，就会产生一个实例，在实例中拿到它的DOM对象
      const app = createApp(Comp)
      // 拿到它的实例,挂载到动态创建的DOM上，vue开发是支持多实例的，可以创建多个实例
      // 因为创建的元素没挂载到BODY上，实际也没有完成dom层的挂载，目的是创建出来的实例的DOM对象要挂载到el上（指令所在的DOM）
      const instance = app.mount(document.createElement('div'))
      // 拿到组件的名称，当多个指令应用于一个元素上时，后面的会覆盖前面的el.instance，导致删除失败
      /*
      * 通过对象多一个维度，解决后面创建的值覆盖前面值的问题 小技巧谨记
      * */
      const name = Comp.name
      // 检测如果el中没有这个字段 那么创建一个对象
      if (!el[name]) {
        el[name] = {}
      }
      // 因为instance在mounted中只创建一次，但是之后会经常用到，要保留起来，如果要在其他的钩子函数也要访问它的话就存在el对象上
      // 这样操作在其他钩子中也可以获取到这个实例
      // 不同的组件，使用不同的el[name],呢么其各自的instance就不同了，比如el['loading'].ins, el['no-result'].ins
      // 当值用一个时候，一个元素上应用两个v-指令 那么后面的指令会覆盖前面的指令
      el[name].instance = instance
      // console.log(binding)
      // 通过binding.arg拿到动态参数,如果组件中有多个参数可以考虑传进来的是一个数组
      const title = binding.arg
      // 如果参数不是空 执行实例中的方法
      if (typeof title !== 'undefined') {
        // 组件内部的方法，如果多个组件，需要统一一致的方法
        instance.setTitle(title)
      }
      // binding.value就是代表指令传递的值
      if (binding.value) {
        append(el)
      }
    },
    // 当组件更新的时候执行，因为指令不是一成不变的比如由v-loading=true变为v-loading=false 就会执行
    updated(el, binding) {
      // 通过binding.arg拿到动态参数
      const title = binding.arg
      const name = Comp.name
      // 如果参数不是空 执行实例中的方法
      if (typeof title !== 'undefined') {
        el[name].instance.setTitle(title)
      }
      // 如果loading前后值不一致
      if (binding.value !== binding.oldValue) {
        // 如果是true那么就插入否则删除
        binding.value ? append(el) : remove(el)
      }
    }
  }
  return directive

  // 元素挂载的操作
  function append(el) {
    const name = Comp.name
    // 根据loading组件样式，是使用absolute，而当el不是fixed或retaive时候给其动态添加定位属性
    const style = getComputedStyle(el)
    // 判断el的样式中有无定位，===-1就是没有 希望v-loading不受样式限制
    if (['absolute', 'fixed', 'relative'].indexOf(style.position) === -1) {
      addClass(el, relativeCls)
    }

    // 因为loading组件生成的实例instance已经赋值给el.instance属性上了，所以在这里可以直接通过el拿到
    // el.instance.$el就是loading组件的DOM对象
    el.appendChild(el[name].instance.$el)
  }

  function remove (el) {
    const name = Comp.name
    removeClass(el, relativeCls)
    el.removeChild(el[name].instance.$el)
  }
}
